miracast uibc详解 您所在的位置:网站首页 miracast 接收器 miracast uibc详解

miracast uibc详解

2024-01-14 12:39| 来源: 网络整理| 查看: 265

在这里插入图片描述 如上图所示,官方wifi display参考架构里有一个uibc的功能一直没用上。目前已知的信息是,这个是用来做反向控制的一个功能,在电视上即可对手机进行控制。我们现在就来研究一下

1. UIBC定义

User Input Back Channel 用户输入反向控制通道(UIBC)是一个可选的WFD特性,实现该扩展功能,有助于用户从WFD sink端控制WFD source端的通信。 wifi display spec文档定义:

4.11 User Input Back Channel The User Input Back Channel (UIBC) is an optional WFD feature that when implemented facilitates communication of user inputs to a User Interface, present at the WFD Sink, to the WFD Source. All UIBC user inputs are packetized using a common packet header and transported over TCP/IP. The user input categories include Generic, and HIDC. The Generic category is used for device agnostic user inputs that are processed at the application level. Generic user inputs are formatted using the Generic Input Body. HIDC is used for user inputs generated by HIDs like remote control, keyboard, etc.[46]. HIDC user inputs are formatted using the HIDC Input Body. 翻译一下 4.11 UIBC UIBC是WFD的可选feature,便于用户在UI上用户交流?展现在sink端,到source端?什么鬼! 所有uibc用户输入被打包成common的包头,然后用tcp/ip进行传输,user input类别包括HIDC和Generic。 Generic类别被用于在应用程序级别处理的与设备无关的用户输入。 HIDC被用于通过HIDs(一类HID类设备)生成的user input,像远程控制,键盘等等,UIDC user inputs被用UIDC Input Body来格式化

虽然翻译的乱七八糟,但还是大概有了点概念。 接下来是

1.1 UIBC的TCP/IP封包形式:

在这里插入图片描述 The fields in the common packet header are described below:

Version (3 bits): The version of the protocol. This field shall be set to 0b000.T (1 bit): Presence of the optional timestamp before the input body, where 0 means the timestamp field does not exist, and 1 means the timestamp field exists.猜测这是一个事件对应的时间戳,可以设置0.时间就不设置了Reserved (8 bits): 预留了8bitsLength (16 bits): 整个 TCP 有效负载的长度,以 8 位为单位,从位偏移量 0 到 UIBC 输入正文的结尾 (包括填充,如果有的话)。Input Category (4 bits): 就是前面提到的Generic或者HIDC,前者用0,后者用1.用来表明当前这条UIBC的类型是哪个。UIBC Input Body: 此字段是Generic输入正文或 HIDC 输入正文,如输入类别字段中所示,包含 描述一个或多个用户输入的信息。 一个用户输入对应一个Generic输入消息或一个 HIDC 消息,具体 取决于所选的输入类别。 此外,该字段应填充为整数16 位的倍数,以便在 Length 字段中具有偶数。 2. UIBC的建立和维护

UIBC是使用RTSP GET_PARAMETER 和SET_PARAMETER消息建立和维护的。 消息序列如下两个Figure 在这里插入图片描述 在这里插入图片描述 WFD Source用于 UIBC 消息事务的 TCP 端口号包含在 wfd-uibc-capability 中RTSP M4 和/或 M14 请求消息中的参数,并且 WFD Source上的该端口应准备好接受在发送后续 RTSP M4 和/或 M14 请求消息之前从 WFD Sink传入的连接包含 wfd-uibc-capability 参数。 一旦建立,WFD Source和 WFD Sink之间的单个 TCP 连接WFD Sink应在 WFD 会话期间 用于所有 UIBC 数据交换。

2.1 UIBC Input Body 2.1.1 Generic input Body Format:

在这里插入图片描述 类型那里,有鼠标/Touch按下抬起,鼠标移动,放大,滚动,旋转等等,具体参考 《Wi-Fi_Display_Technical_Specification_v2.1_0》表15即可。第三列的Describe这里基本就是显示区域的坐标了。

2.1.2 HIDC Input Body Format

在这里插入图片描述 HIDC Input Path如表23所示,有红外线,USB,蓝牙,Zigbee,Wi-Fi等 HID Type有键盘鼠标游戏手柄,相机,手势,远程控制等

3. source端实例分析

github中 hw手机fw代码uibc部分研究 代码如下:反编译出来的代码 https://github.com/SivanLiu/HwFrameWorkSource/blob/5b92ed0f1ccb4bafc0fdb08b6fc4d98447b754ad/Mate20_9_0_0/src/main/java/com/android/server/display/HwUibcReceiver.java(疑似华为手机source uibc的实现)

3.1 创建UIBC接收器

WifiDisplayController.this.mUibcInterface.createReceiver,调用的是HwUibcReceiver中的createReceiver方法 在这里插入图片描述 由于这个HwUibcReceiver继承自HandlerThread,所以这里调用的start方法会启动线程.线程里做什么还没找到。 然后调用了CreateHandler方法,初始化了mHandler为UibcHandler 在这里插入图片描述 msg为1的时候,干的事情大概是初始化的,log可以看出是UIBC_START了。并且在后面有调用 Socket unused2 = HwUibcReceiver.this.mSocket = HwUibcReceiver.this.mServer.accept(); 做一个等待连接 socket的初始化在前面HwUibcReceiver构造函数中: 在这里插入图片描述 有连接过来之后使用这条socket初始化了mInput BufferedInputStream unused4 = HwUibcReceiver.this.mInput = new BufferedInputStream(HwUibcReceiver.this.mSocket.getInputStream()); 然后便是 HwUibcReceiver.this.receiveEvent(); 在这里插入图片描述 发了msg what为2.即是 在这里插入图片描述 主要就是read从socket收到的数据,然后consumePacket进行消费,然后接着调用receiveEvent。 大概流程清楚了。 那我们接着分析消费的流程。消费 = 解析 + 处理

3.2 消费socket包consumePacket

在这里插入图片描述 跟协议相对应,先看当前的input category是哪种,前面协议部分已经说过了,0是generic事件,1是hid事件。很遗憾,这里hid调用了native方法,而对应的native代码肯定是无法被反编译的。那这里就先不分析了。我们先研究Generic Input

3.3 处理Generic Input

在这里插入图片描述 对前文提到的Generic类型的input有所呼应。这个函数大概做的事情

3.4 解析输入正文resolveInputBody

在这里插入图片描述 payload的第一位表明了inputType.协议table15中有定义,按下抬起,滚动,缩放等。 后面2位计算出bodyLength之后向前移动三字节。最后面那个mParseBytes应该是全局记录当前解析的位置的变量。

3.5 解析事件resolveEvent

在这里插入图片描述 根据inputType分别调用createKeyEvent或者createTouchEvent来创建不同的InputEvent对象。 我们先看简单的

3.5.1 创建key事件createKeyEvent

在这里插入图片描述 请注意这里new的KeyEvent对象其实继承自InputEvent. 这里调用的构造函数是这个: 在这里插入图片描述 按下时间点,事件发生时间点,action这个大家都很熟悉了,code也是,调用者传入的 b = payload[index + 5];说明偏移5个字节(这里为什么偏移5?)指示的就是事件的code,即这些: 在这里插入图片描述

3.5.2 创建touch事件createTouchEvents

在这里插入图片描述 遍历payload,然后调用doTranslateCoord翻译坐标,虽然这里传入参数和解析过程应该都很重要,但是看着比较复杂。简单来说就是根据sink传过来的左边做了一个坐标点的转换。

3.6 注入input事件injectInputEvent

遍历所有的Event,然后分别调用InputManager的injectInputEvent接口,注入事件 在这里插入图片描述 也就是说构造一个InputEvent对象送进入,只要这个event合理合法,系统就会作出响应。

4. sink端实例分析(控制台版本)

代码地址: https://github.com/albfan/miraclecast/tree/master/src/uibc 看上去只是一个控制台的二进制实现,并不是电视端的,但是大致原理应该是相通的 在这里插入图片描述

4.1 socket连接

在这里插入图片描述 输入两个参数,参数1是ip地址,参数2是端口 在这里插入图片描述 创建一路TCP连接,跟前面source端对应起来理解 UIBC这种case里,被反向控制的设备是server,而大屏端其实是client

if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) uibcmessage = buildUibcMessage(GENERIC_TOUCH_DOWN, buffer, 1, 1); } else if (type == '3' || type == '4') { uibcmessage = buildUibcMessage(GENERIC_KEY_DOWN, buffer, 1, 1); } else { if (!daemon) { printf("unknow event type: %s", buffer); } continue; } 4.2 打包Input Body 组包过程如下 // UibcMessage结构体定义 typedef struct { char* m_PacketData; size_t m_PacketDataLen; bool m_DataValid; } UibcMessage; UibcMessage buildUibcMessage(MessageType type, const char* inEventDesc, double widthRatio, double heightRatio) { UibcMessage uibcmessage; uibcmessage.m_PacketData = NULL; uibcmessage.m_PacketDataLen = 0; uibcmessage.m_DataValid = false; switch (type) { case GENERIC_TOUCH_DOWN: case GENERIC_TOUCH_UP: case GENERIC_TOUCH_MOVE: getUIBCGenericTouchPacket(inEventDesc, &uibcmessage, widthRatio, heightRatio); break; case GENERIC_KEY_DOWN: case GENERIC_KEY_UP: getUIBCGenericKeyPacket(inEventDesc, &uibcmessage); break; case GENERIC_ZOOM: getUIBCGenericZoomPacket(inEventDesc, &uibcmessage); break; case GENERIC_VERTICAL_SCROLL: case GENERIC_HORIZONTAL_SCROLL: getUIBCGenericScalePacket(inEventDesc, &uibcmessage); break; case GENERIC_ROTATE: getUIBCGenericRotatePacket(inEventDesc, &uibcmessage); break; }; return uibcmessage; } 根据不同的type构建不同的uibcmessage。我们先看一个看上去更简单的getUIBCGenericKeyPacket(touch还要考虑坐标,相对复杂一些) UibcMessage* uibcmessage) { char* outData = uibcmessage->m_PacketData; int32_t typeId = 0; int32_t uibcBodyLen = 0; int32_t genericPacketLen = 0; int32_t temp = 0; size_t size; // 这个inEventDesc从前面看,是由控制台输入的字符串 // 从这里来看,应该是使用逗号分割开的一堆字符串 // 并且str_split函数还算出了事件的个数 char** splitedStr = str_split((char*)inEventDesc, ",", &size); if (size > 0) { // 这里说明一个事件至少三段 // typeId, key Code,另外一个是啥? if (((int)size) % 3 != 0) { log_error("getUIBCGenericKeyPacket (%s)", "bad input event"); return; } //由于splitedStr是一个指针数组,所以 //i++之后 //splitedStr + i就指向了下一个逗号隔开的元素了 int i; for (i = 0; i case 0: { // 第一段是一个整数,typeid typeId = atoi(*(splitedStr + i)); genericPacketLen = 5; uibcBodyLen = genericPacketLen + 7; // Generic header legth = 7 // 总共13字节的buffer outData = (char*)malloc(uibcBodyLen + 1); // UIBC header outData[0] = 0x00; //Version (3 bits),T (1 bit),Reserved(8 bits),Input Category (4 bits) outData[1] = 0x00; // 低八位放第一个字节 outData[2] = (uibcBodyLen >> 8) & 0xFF; //Length(16 bits) // 高八位放在第二字节 outData[3] = uibcBodyLen & 0xFF; //Length(16 bits) //Generic Input Body Format outData[4] = typeId & 0xFF; // Type ID, 1 octet outData[5] = (genericPacketLen >> 8) & 0xFF; // Length, 2 octets outData[6] = genericPacketLen & 0xFF; // Length, 2 octets outData[7] = 0x00; // reserved break; } case 1: { sscanf(*(splitedStr + i), " 0x%04X", &temp); if (temp == 0) { outData[8] = 0x00; outData[9] = 0x00; //这里不是应该直接return吗? } // 填充keyCode // 没猜错的话,这个keyCode应该完全对应Android keyevent中定义的那一套 log_info("getUIBCGenericKeyPacket key code 1=[%d]\n", temp); outData[8] = (temp >> 8) & 0xFF; outData[9] = temp & 0xFF; break; } case 2: { sscanf(*(splitedStr + i), " 0x%04X", &temp); if (temp == 0) { outData[10] = 0x00; outData[11] = 0x00; } outData[10] = (temp >> 8) & 0xFF; outData[11] = temp & 0xFF; break; } default: { } break; } free(*(splitedStr + i)); } } free(splitedStr); uibcmessage->m_DataValid = true; uibcmessage->m_PacketData = outData; uibcmessage->m_PacketDataLen = uibcBodyLen; 4.3 发送带input body的uibc包 r = sendUibcMessage(&uibcmessage, sockfd); if (r) { return r; } close(sockfd);

前面组完包之后就调用sendUibcMessage进行发送,第二个参数为4.1中创建的socket连接fd。

int sendUibcMessage(UibcMessage* uibcmessage, int sockfd) { ssize_t n; printf("sending %zu bytes\n", uibcmessage->m_PacketDataLen); n = write(sockfd, uibcmessage->m_PacketData , uibcmessage->m_PacketDataLen); if (n if (FeatureOption.MTK_WFD_SINK_UIBC_SUPPORT) { mGlobal.sendUibcInputEvent(input); } } 5.2 frameworks/av /av/media/libmediaplayerservice/RemoteDisplay.cpp status_t RemoteDisplay::sendUibcEvent(const String8& eventDesc) { const char* pEventDes = eventDesc.string(); // 取一个字节。直接转换 int type = *pEventDes; if (*(pEventDes+1) != ',') return BAD_VALUE; ALOGD("sendUibcEvent: type=0x%X", type); switch (type) { //0x30是数字0字符的ascii码 case 0x30: case 0x31: case 0x32: mSink->sendUIBCGenericTouchEvent(pEventDes); break; //0x33是数字3字符的ascii码 case 0x33: case 0x34: mSink->sendUIBCGenericKeyEvent(pEventDes); break; default: return BAD_VALUE; } return OK; } 5.4 frameworks-ext/av status_t WifiDisplaySink::sendUIBCGenericTouchEvent(const char * eventDesc) { status_t err; if (mUibcClientHandler == NULL) return -1; err = mUibcClientHandler->sendUibcMessage(mNetSession, UibcMessage::GENERIC_TOUCH_DOWN, eventDesc); return err; }

到这里就很全面了,过程推测应该和前面4中控制台的基本一样。毕竟函数名字都一模一样。也不知道谁抄的谁。 还有个遗留问题,应用是怎么调用的sendUibcInputEvent接口。找到的结果如下

5.5 packages/apps/Settings // /src/com/mediatek/settings/wfd/WfdSinkSurfaceFragment.java @Override public boolean onTouchEvent(MotionEvent ev) { ... StringBuilder eventDesc = new StringBuilder(); eventDesc.append( //如果是uo的时候,这里就用 //GENERIC_INPUT_TYPE_ID_TOUCH_UP //这里就不用ascii码了 //因为0转换完之后就是0x30了 String.valueOf(GENERIC_INPUT_TYPE_ID_TOUCH_DOWN)) .append(","); eventDesc.append(getTouchEventDesc(ev)); sendUibcInputEvent(eventDesc.toString()); ... } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { ... int asciiCode = event.getUnicodeChar(); if (asciiCode == 0 || asciiCode WfdRtspUibcPacket* newPacket = new WfdRtspUibcPacket(); currEventType = WfdRtspProtocol::instance().mUibcEventType; newPacket->setCategory(WfdRtspUibcPacket::UIBC_HIDC); if (WfdRtspProtocol::instance().mUibcEventType == HIDC_EVT_TYPE_MOUSE) newPacket->setHidType(WfdRtspUibcPacket::HID_TYPE_MOUSE); else newPacket->setHidType(WfdRtspUibcPacket::HID_TYPE_KEYBOARD); UibcQueue.push_back(newPacket); ... WfdRtspUibcPacket* uibcPacket = UibcQueue.front(); UibcQueue.pop_front(); if (WfdRtspProtocol::instance().mUibcEventType == HIDC_EVT_TYPE_MOUSE) { /* -------------------------------------------------------------------- HID Mouse/USB -------------------------------------------------------------------*/ // displacement是移位的意思 int disps[2] = {5, 5}; /* x-displacement, y-displacement */ // 循环30次 for (round_count = 0; round_count WFD_LOG_ERR("Error! send UIBC Mouse data1 error !"); break; } total_counts ++; disps[0] += 5; disps[1] += 5; // sleep 500ms usleep(DELAY_BETWEEN_EACH_EVENT_MS * 1000); ... } } }

这段代码主要是循环发送了一个鼠标事件,坐标值从5,5一直自增加5,增加三十次,看上去是轨迹是一个斜率为1的一条斜线。 继续看下这个关键方法getTranData,这个方法完成的就是组包操作

int WfdRtspUibcPacket::getTranData(unsigned char * buffer,int len, void *data) { ... else if (mCategory == UIBC_HIDC) { struct uibc_packet_s upkt; unsigned short *usptr; unsigned char *buf_ptr = NULL, *buf_head_ptr = NULL; memset(buffer, 0, len); memset(&upkt, 0, sizeof(struct uibc_packet_s)); // 构造包头 upkt.hdr.version = mVersion; upkt.hdr.T = 0; upkt.hdr.rsvd = 0; upkt.hdr.input_category = mCategory; // 转换网络顺序 /* need to convert first ushort bytes to network order */ usptr = (unsigned short *)&upkt.hdr; *usptr = htons(*usptr); if (mHidType == HID_TYPE_MOUSE) { struct hid_mouse_input_report_s *mouse = NULL; //坐标指针 int *disps = (int *)data; // 必须走USB? // 其他选项有INFRARED红外线 // BT WIFI等等 // 我理解这里是采用数据流模拟USB输入的意思 upkt.u_body.uibc_hidc.input_path = HID_INPUT_PATH_USB; upkt.u_body.uibc_hidc.hid_type = HID_TYPE_MOUSE; upkt.u_body.uibc_hidc.usage = 0; /* not Report Descriptor */ upkt.u_body.uibc_hidc.length = htons(HIDC_USB_MOUSE_VALUE_LEN); // 构造鼠标hid report descriptor // 从这里来看,mouse直接指向了hidc_value的首地址 mouse = (struct hid_mouse_input_report_s *)&upkt.u_body.uibc_hidc.hidc_value[0]; mouse->x_disp = (unsigned char)disps[0]; mouse->y_disp = (unsigned char)disps[1]; // 这里提示要注意字节对齐的问题 /* copy result to buffer */ /* Note that because alignment issue that after HIDC "usage filed" will have one additional byte, so we manage the buffer by copying. It is better to use pragma though. */ buf_ptr = buffer; buf_head_ptr = buf_ptr; // 跳过uibc包头 buf_ptr += sizeof(struct uibc_packet_hdr_s); *buf_ptr++ = upkt.u_body.uibc_hidc.input_path; *buf_ptr++ = upkt.u_body.uibc_hidc.hid_type; *buf_ptr++ = upkt.u_body.uibc_hidc.usage; memcpy(buf_ptr, (unsigned char *)&upkt.u_body.uibc_hidc.length, sizeof(upkt.u_body.uibc_hidc.length)); // 跳到hidc buf_ptr += sizeof(upkt.u_body.uibc_hidc.length); // 填充hid report descriptor memcpy(buf_ptr, mouse, sizeof(struct hid_mouse_input_report_s)); buf_ptr += sizeof(struct hid_mouse_input_report_s); // 4+9 retLen = UIBC_HDR_LEN + UIBC_HIDC_BODY_LEN_MOUSE; // 主机字节序变为网络字节序 // 网络字节顺序采用big-endian排序方式 upkt.hdr.length = htons((unsigned short)retLen); memcpy(buf_head_ptr, (unsigned char *)&upkt.hdr, sizeof(struct uibc_packet_hdr_s)); } } }

上面这些复杂的结构体看名字头都晕。可以看下定义

struct uibc_packet_s { struct uibc_packet_hdr_s hdr; // 这里是二选一的意思 union { struct uibc_body_generic_s uibc_gen; struct uibc_body_hidc_s uibc_hidc; } u_body; }; struct uibc_packet_hdr_s { // 冒号这是c语言的写法,位域 // 表示占用4位 unsigned short input_category : 4; /* Input Category: b12~15 */ unsigned short rsvd : 8; /* Reserved: b4~11 */ unsigned short T : 1; /* T: b3 */ unsigned short version : 3; /* version: b0~2 */ unsigned short length; /* Length: b16~31 */ /* unsigned short timestamp; */ /* Timestamp(Optional): b32~47*/ }; // 这个是generic的 struct uibc_body_generic_s { unsigned char input_type_id; unsigned short length; unsigned char describe[64]; }; // 这一部分才是属于hidc的 struct uibc_body_hidc_s { unsigned char input_path; unsigned char hid_type; unsigned char usage; unsigned short length; // 如果是描述符这种形式,hid_mouse_input_report_s是直接放在这里的 unsigned char hidc_value[64]; }; struct hid_mouse_input_report_s { unsigned char buttons; unsigned char x_disp; unsigned char y_disp; unsigned char dev_specific; };

到这里,理论上来说功能已经可以实现了。按照固定格式组好13个字节(头一共32位,四个字节。body9个字节uibc_body_hidc_s由1+1+1+2+4(report desc)组成)的包,然后通过socket发送出去应该就好了。

7.2 实际开发中的坑

实际开发下来,发现hid_mouse_input_report_s这个结构体,后面dev_specifics字段应该拿掉。参考: 在这里插入图片描述 在这里插入图片描述 图片来自https://blog.csdn.net/sinat_37343534/article/details/116446980 修改完之后,windows10电脑确实响应了。但是位置不对。坐标各种乱跳。毕竟x,y坐标每个只有一个字节的位置。0-255的范围肯定无法和1920*1080分辨率的电视做对应的。 到这里我突然想起在哪里看到过,好像可以在某一位指定坐标值是否超过255。 在这里插入图片描述

搜索之后发现确实有这个说法。也就是说前面一个字节的button其实八个byte都是有意义的。又去翻了下Wi-Fi_Display_Technical_Specification_v2.1_0,又发现点新东西 在这里插入图片描述 大概意思是说: HID 报告描述符描述了其关联的 HID 输入报告的格式。 对于每个 HID 接口类型和 HID 类型组合,WFD 接收器应在发送 HID 输入之前将其关联的 HID 报告描述符发送到 WFD 源向 WFD 源报告。 WFD Sink 可以在多个场合向 WFD Source 发送 HID 报告描述符以确保 WFD 源具有最新的 HID 报告描述符。 指定了 USB 键盘和鼠标 HID 输入报告的默认描述符。 如果 HID 输入报告 WFD 接收器发送基于 USB 键盘和鼠标的默认报告描述符,WFD 接收器不需要发送HID 报告描述符。 键盘的默认 USB HID 报告描述符设置为 E.6 节中指定的描述符[21] 中的“报告描述符(键盘)”。 鼠标的默认 USB HID 报告描述符设置为在[21] 中的 E.10 节“报告描述符(鼠标)”。 原来前面截图中的e10来自这里(Wi-Fi_Display_Technical_Specification_v2.1_0引用的一个文档): [21] “Universal Serial Bus Specification - USB Device Class Definition for Human Interface Devices”, Version 1.11, Jun.2001 (文档地址文后有附)。 这里有个疑问,如果usage设置0x01.不发送报告描述符,那坐标如何送给source? 结果,我又看了一下usage的描述 This field indicates the usage of the HIDC value field in this table. The value of this field shall be set to 0x00 if the HIDC value field contains a HID input report. The value of this field shall be set to 0x01 if the HIDC value field contains a HID report descriptor. 仔细看了,才发现 0x00 hidc value field包含的是hid input report 0x01 hidc value field包含的是hid report desctiptor 而前面又说到E.10表格就已经是默认report desctiptor。也就是说如果设置0x00那就直接使用E.10中默认好的report descriptor,自己无需再做复杂的封装。而从https://blog.csdn.net/sinat_37343534/article/details/116446980 这位前辈的探索来看,只需在descriptor的input位置提供值即可。 继续研究这个坐标值的问题,前述图片中有提到x,y只是坐标变化量。怪不得我每次点击同一个位置,好像鼠标指针的确是按照固定的步进去移动。

7.3实验结果 button位符号位字节没用,设置了不识别,直接在坐标位置赋值实际计算出来的正/负delta就可以。button位指示三个button按下的位置有用,如果不设置任何值,组指针只会move,(手机上)如果设置了 #define MOUSE_LEFT_KEY_MASK 0x1 屏幕会被拖动。推测在电脑上,RIGHT_KEY也是会响应的。 参考资料:

一些相关博客 https://blog.csdn.net/sinat_37343534/article/details/116306013 https://m.xuejianbihua.com/item/xYTE3ZDUyODVhNzJjMTY4MDk4YzhmMmY2u.html https://blog.csdn.net/sinat_37343534/article/details/116446980 控制台版本的sink端代码实现 https://github.com/albfan/miraclecast/tree/master/src/uibc 疑似华为手机source uibc的实现代码 https://github.com/SivanLiu/HwFrameWorkSource/blob/5b92ed0f1ccb4bafc0fdb08b6fc4d98447b754ad/Mate20_9_0_0/src/main/java/com/android/server/display/HwUibcReceiver.java lg电视的实现效果 https://www.youtube.com/watch?v=Q-I9g9g_21g 苏州必捷的实现效果 https://zhuanlan.zhihu.com/p/437928388 这下面有client代码 ~/code/giant-mtk/vendor/mediatek/proprietary_tv/open/hardware/wfd_client https://www.usb.org/hid usb的hid鼠标键盘报告描述符: http://t.zoukankan.com/zongzi10010-p-10155333.html https://www.usb.org/sites/default/files/hid1_11.pdf Android鼠标源码研究(五)–输入事件处理 https://blog.51cto.com/u_15067266/2908866 Tutorial about USB HID Report Descriptors https://eleccelerator.com/tutorial-about-usb-hid-report-descriptors/ HID鼠标描述符 https://www.luliang.vip/archives/4.html



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有